; Second stage loader for FAT12 floppy

[bits 16]
[org 0x7c00]

; *******************************************************************
; D E C L A R A T I O N S
; *******************************************************************

struc FAT12BPB
	.oem_name:				resb	8
	.bytes_per_sector:		resw	1
	.sectors_per_cluster:	resb	1
	.reserved_sector_count:	resw	1
	.table_count:			resb	1
	.root_entry_count:		resw	1
	.total_sectors_16:		resw	1
	.media_type:			resb	1
	.table_size_16:			resw	1
	.sectors_per_track:		resw	1
	.head_side_count:		resw	1
	.hidden_sector_count:	resd	1
	.total_sectors_32:		resd 	1
; extended fat12 and fat16 stuff
	.bios_drive_num:		resb	1
	.reserved1:				resb	1
	.boot_signature:		resb	1
	.volume_id:				resd	1
	.volume_label:			resb	11
	.fat_type_label:		resb	8
endstruc

struc BOOTDATA
	.lowmem:		resw	1
	.himem:			resw	1
	.RAMpages:		resw	1
	.disk:			resw	1
endstruc

; *******************************************************************
; E N D   O F   D E C L A R A T I O N S
; *******************************************************************

; *******************************************************************
; A S S E M B L Y   C O D E
; *******************************************************************

; *******************************************************************
; Routine: main

main:
	mov [bootdata + BOOTDATA.disk], dx
	mov ax, [bx + FAT12BPB.sectors_per_track]
	mov [sectors_per_track], ax
	mov ax, [bx + FAT12BPB.head_side_count]
	mov [head_side_count], ax
	mov ax, [bx + FAT12BPB.bytes_per_sector]
	mov [bytes_per_sector], ax

	xor ax, ax				; Nullify the A-register.
	int 0x12				; Switch to the BIOS (= request low memory size).
	jc .error				; The carry flag is set, it failed.
	test ax, ax				; Test the A-register with itself.
	jz .error				; The zero flag is set, it failed.

	; AX = amount of continuous memory in kB starting from 0.
	mov [bootdata + BOOTDATA.lowmem], ax
	
	xor cx, cx
	xor dx, dx
	mov ax, 0xe801
	int 0x15				; request upper memory size
	jc .error
	cmp ah, 0x86			; unsupported function
	je .error
	cmp ah, 0x80			; invalid command
	je .error
	jcxz .useax				; was the cx result invalid?
 
	mov ax, cx
	mov bx, dx
.useax:
	test ax, ax				; If no upper memory available go out
	jz .error
	
	mov [bootdata + BOOTDATA.himem], ax		; AX = number of contiguous Kb, 1M to 16M
	mov [bootdata + BOOTDATA.RAMpages], bx	; BX = contiguous 64Kb pages above 16M

	mov si, msg
	call print

	mov si, lmmsg
	call print

	mov ax, [bootdata + BOOTDATA.lowmem]
	mov si, tmpstr
	call axtostring			; Convert memory counter to string
	
	mov si, tmpstr
	call print				; Print low memory
	
	mov si, kbmsg
	call print				; Print KiB

	mov ax, [bootdata + BOOTDATA.himem]
	mov si, tmpstr
	call axtostring			; Convert ax into string
	
	mov si, ummsg
	call print				; Print Upper memory
	
	mov si, tmpstr
	call print
	
	mov si, kbmsg
	call print
	
	mov ax, [bootdata + BOOTDATA.RAMpages]
	mov si, tmpstr
	call axtostring
	
	mov si, umpmsg
	call print				; Print Upper memory Pages
	
	mov si, tmpstr
	call print
	
	mov si, crnlmsg
	call print
	
	mov si,	bsKernelMsg
	call print	

; *******************************************************************
; Search for KERNEL file entry in root directory
	mov	bx, [bsRootEntry]
.searchkernel:
; Search kernel in /
; Scan all entries
	mov	al, [bx]
	cmp	al, 0				; If byte 0 in entry is 0 exit and halt
	jz	.error
	cmp	al, 0xE5			; If byte 0 in entry is 0xE5 ignore entry
	jz	.searchkernel_continue
	mov	al, [bx + 11]		; If byte 11 in entry matches one of these: 0x08/0x10 ignore entry
	test al, 0x18
	jnz	.searchkernel_continue
;	We found a FILE entry
;	If byte 0-10 contain the string 'KERNEL  SYS' exit and load the file
	mov	cx, 10				; Set counter (10 to 0)
	mov	si, bsKernelName	; Set string to search for in source index
	mov	di, [bsRootEntry]	; Set string to search in in destination index
.compname:
	mov al, [ds:si]			; Load next char of both strings
	mov bl, [es:di]
	cmp al, bl				; If compare is not OK scan next entry
	jnz .searchkernel_continue
	jcxz .searchkernel_end	; If counter is 0 so we got Success
	dec cx					; Update counters
	inc	si
	inc	di
	jmp .compname			; Compare next char
.searchkernel_continue:
	mov	bx, [bsRootEntry]	; Select next entry
	add bx, 32
	mov	[bsRootEntry], bx
	jmp	.searchkernel
.searchkernel_end:
; Search for LOADER End
; *******************************************************************
	
	; Before loading the kernel we need unreal mode
	call voodoo				; Enter voodoo mode

	mov si, bsKernLoadMsg
	call print

; *******************************************************************
; Load kernel, here we read secors to a specific location and then we copy
; to higher memory one by one.
;	mov	bx, [bsRootEntry]	; Load kernel size in ax
;	mov eax, [bx + 28]
;	cmp eax, 0x00080000
;	jg .upperkernel
;	mov ebx, 0x00010000			; Load buffer for kernel - kernel size < 500k copy to 0x00010000
;	jmp .krnlbuffer
;.upperkernel:
	mov ebx, 0x00100000			; Load buffer for kernel
;.krnlbuffer:
	mov [bsBuffer], ebx
	mov	bx, [bsRootEntry]	; Load first sector number in ax
	mov ax, [bx + 26]
.loadkernel:
	push ax					; Store for next calculation
	add ax, 31				; Get real LBA
	mov bx, tempsector
	call readsectorLBA

	and eax, 0x0000ffff
	mov ecx, eax
	xor ebx, ebx
	mov esi, tempsector
	mov edi, [bsBuffer]
	rep a32 movsb

	mov	ebx, [bsBuffer]		
	add	ebx, eax
	mov	[bsBuffer], ebx

	pop ax					; Get current entry
	mov cx, ax
	and cx, 0x0001
	mov bx, ax
	shr ax, 1				; Divide by 2
	add ax, bx				; And add 
	mov bx, [bsFatPtr]
	add bx, ax
	mov ax, [bx]
	jcxz .iseven
	shr ax, 4
	jmp .isodd
.iseven:
	and ax, 0x0fff
.isodd:
	cmp ax, 0xff7			;
	je .error				; Bad sector :(
	cmp ax, 0xff8			; Test if last sector
	jge .loadkernel_end		; Jupt to end
	jmp .loadkernel
.loadkernel_end:
; Load stage 2 End
; *******************************************************************
	
	; Guess.... everything is ready now
	; so we can enter protected mode
	; TODO: Pass BOOTDATA to kernel!!!
	cli
	lgdt [flatgdtinfo]
	mov eax, cr0
	or eax, 1
	mov cr0, eax
	
	; Clear all segments and jump into the kernel
	mov ax, 0x10
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	mov ss, ax
	mov eax, dword bootdata			; Pass parameter to kernel
	jmp dword 8:0x00100000		; Code selector is 8 (first valid entry of GDT)

.error:
	mov si, errmsg			; Error !
	call print
	hlt
; main End
; *******************************************************************

; *******************************************************************
; Routine: print - wraps int 10h
; Parameters: SI is the string to print
print:
.ch_loop:
	lodsb
	or al, al ; zero=end or str
	jz .return   ; get out
	mov ah, 0x0E
	int 0x10
	jmp .ch_loop
.return:
	ret
; print End
; *******************************************************************

; *******************************************************************
; Routine: axtostring AX is AX, SI is destination string
axtostring:
	mov bl, '0'
	mov [si], bl
	mov bl, 'x'
	mov [si + 1], bl
	xor bx, bx
	mov bl, ah
	shr bl, 4
	cmp bl, 0x0A
	jb .lt10_1
	add bl, 0x37
	jmp .ht10_1
.lt10_1:	or bl, 0x30
.ht10_1:
	mov [si + 2], bl
	mov bl, ah
	and bl, 0x0f
	cmp bl, 0x0A
	jb .lt10_2
	add bl, 0x37
	jmp .ht10_2
.lt10_2:	or bl, 0x30
.ht10_2:
	mov [si + 3], bl
	mov bl, al
	shr bl, 4
	cmp bl, 0x0A
	jb .lt10_3
	add bl, 0x37
	jmp .ht10_3
.lt10_3:	or bl, 0x30
.ht10_3:
	mov [si + 4], bl
	mov bl, al
	and bl, 0x0f
	cmp bl, 0x0A
	jb .lt10_4
	add bl, 0x37
	jmp .ht10_4
.lt10_4:	or bl, 0x30
.ht10_4:
	mov [si + 5], bl
	
	mov al, 0
	mov [si + 6], al
ret
; axtostring End
; *******************************************************************

; *******************************************************************
; Routine: readsectorLBA AX is LBA BX is destination, returns ax=bytes read
readsectorLBA:
	push bx					; Remember address (I suck at assembly)
	mov bx, [sectors_per_track]
	cdq
	div bx					; ax = Temp = LBA/sectors_per_track
	inc dx					; Sector = (LBA % (sectors_per_track)) + 1
	mov [bsSector], dx
	mov bx, [head_side_count]
	cdq
	div bx					; al = Cylinder = Temp / (head_side_count)
							; dx = Head = Temp % (head_side_count)
	shl dx, 8				; dh = dl = head
	mov dl,	[bootdata + BOOTDATA.disk]	; Drive
	mov ch,	al				; Cilinder
	mov cl, [bsSector]		; CL = Sector | ((cylinder >> 2) & 0xC0);
	shr al, 2
	and al, 0xC0
	or	cl, al
	
;	mov bx, 0x0000			; Address ES:BX
;	mov es,	bx				; Buffer Address Pointer
	pop bx					; Get address
	
	mov al, 0x01			; Sectors to read
	mov ah,	0x02			; Read Sectors From Drive
	int 0x13
	mov ax, [bytes_per_sector]
	ret
; readsectorLBA End
; *******************************************************************

; *******************************************************************
; Routine: voodoo, enters unreal mode
voodoo:
   cli						; no interrupts
   push ds					; save real mode
 
   lgdt [gdtinfo]			; load gdt register
 
   mov eax, cr0				; switch to pmode by
   or  al, 1				; set pmode bit
   mov cr0, eax
 
   mov bx, 0x08				; select descriptor 1
   mov ds, bx				; 8h = 1000b
 
   and al, 0xFE				; back to realmode
   mov cr0, eax				; by toggling bit again
 
   pop ds					; get back old segment
   sti
   ret
; voodoo End
; *******************************************************************

; *******************************************************************
; E N D   O F   A S S E M B L Y   C O D E
; *******************************************************************

; *******************************************************************
; D A T A
; *******************************************************************

; *******************************************************************
; Global descriptor table (used by voodoo)
gdtinfo:
	dw gdt_end - gdt - 1	; last byte in table
	dd gdt					; start of table
gdt dd 0, 0					; entry 0 is always unused
flatdesc db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0
gdt_end:
; End Global descriptor table (used by voodoo)
; *******************************************************************

; *******************************************************************
; Global descriptor table (used to specify flat memory)
flatgdtinfo:
	dw flatgdt_end - flatgdt - 1	; last byte in table
	dd flatgdt					; start of table
flatgdt dd 0, 0					; entry 0 is always unused
		db 0xff, 0xff, 0, 0, 0, 10011010b, 11001111b, 0		; Code descriptor
		db 0xff, 0xff, 0, 0, 0, 10010010b, 11001111b, 0		; Data descriptor
flatgdt_end:
; End Global descriptor table (used by voodoo)
; *******************************************************************

bsBuffer:			resd	1
sectors_per_track:	resw	1
head_side_count:	resw	1
bytes_per_sector:	resw	1
bsFatPtr:		dw		0x0A00
bsRootEntry:	dw		0x2E00
bsSector:		resw	1
bootdata:		istruc BOOTDATA
					dw 0, 0, 0, 0
				iend
tmpstr:			resb 256
msg:			db 'Buer Research Operating System Bootloader', 13, 10, 0
lmmsg:			db 'Low Memory: ', 0
ummsg:			db 'Upper Memory: ', 0
umpmsg:			db '64K Pages Above 16M: ', 0
kbmsg:			db ' KiB', 13, 10, 0
bsKernelMsg:	db 'Searching for kernel...', 13, 10, 0
bsKernLoadMsg:	db 'Loading kernel...', 13, 10, 0
errmsg:			db 'There was an error during init, or your system does not meet the requirements.', 0
crnlmsg:		db 13, 10, 0
bsKernelName:	db 'KERNEL  SYS'
periodmsg:		db '.', 0
msgdbg1:	db 'I am here 1', 13, 10, 0
msgdbg2:	db 'I am here 2', 13, 10, 0
msgdbg3:	db 'I am here 3', 13, 10, 0
tempsector:		; Here we read kernel chunks
; *******************************************************************
; E N D   O F   D A T A
; ***********************************************************
